//using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System;
using System.Text.RegularExpressions;

public class FuzzyLogic {
	
	public float[] inputs;
	public float[] outputs;
	private static int INPUTNUM = 13;
	private static int OUTPUTNUM = 9;
	private List<Rule> rules;
	private float[] singletons;
	private float defuzzedDIR;
	private float defuzzedSPEED;
	private float defuzzedATTACK;

    public FuzzyLogic() {
        Start();
    }
	
	// Use this for initialization
	void Start() {
		inputs = new float[INPUTNUM];
		outputs = new float[OUTPUTNUM];
		rules = new List<Rule>();
		
		singletons = new float[]{
			// DIR_FOR,	DIR_BACK, DIR_SIDE,
			2, 5, 8,
			// SPEED_NONE, SPEED_SLOW, SPEED_FAST,
			2, 5, 8,
			// ATTACKRATE_SLOW, ATTACKRATE_MEDIUM, ATTACKRATE_FAST
			2, 5, 8
		};
		
		// List of Rules
		// Rule 1
		// if damage and health then speed_slow
        //Rule r = new Rule(1, OutputName.SPEED_SLOW, this);
        //r.setTerm(0, new InputName[]{InputName.DAMAGE, InputName.HEALTH}, new bool[]{false, false});
        //rules.Add(r);
		
        //// Rule 2
        //// if damage and health or health and playerposition then attackrate
        //r = new Rule(2, OutputName.ATTACKRATE_SLOW, this);
        //r.setTerm(0, new InputName[]{InputName.DAMAGE, InputName.HEALTH}, new bool[]{false, false});
        //r.setTerm(1, new InputName[]{InputName.HEALTH, InputName.PLAYERPOS}, new bool[]{false, false});
        //rules.Add(r);
		
        //// Rule 3
        //// if damage and health then dir
        //r = new Rule(1, OutputName.DIR_SIDE, this);
        //r.setTerm(0, new InputName[]{InputName.DAMAGE, InputName.PLAYERPOS}, new bool[]{false, false});
        //rules.Add(r);
		
        //inputs[(int)InputName.DAMAGE] = 0.75f;
        //inputs[(int)InputName.HEALTH] = 0.5f;
        //inputs[(int)InputName.PLAYERPOS] = 0.25f;
		
		int count = 0;
		
		foreach (float o in outputs) {
            //Debug.Log("output[" + count + "] = " + o);
			count++;
		}
	}

    internal void addRule(Rule r) {
        rules.Add(r);
    }
	
	static public float AND(params float[] objects) {
		float x = 1;
		foreach (float o in objects) {
			x = Math.Min(x,o);	
		}
		return x;
	}
	
	static public float OR(params float[] objects) {
		float x = 0;
		foreach (float o in objects) {
			x = Math.Max(x,o);	
		}
		return x;
	}
	
	static public float NOT(float i) {
		return 1-i;	
	}
	
    public void addInputs(params float[] inputs) {
        this.inputs = inputs;
    }

	public void evalutate() {
		for (int i = 0; i < outputs.Length; i++) {
			outputs[i] = 0;	
		}
		foreach (Rule r in rules) {
			float val = r.evalRule();
            int output = r.getOutput();
			outputs[output] = OR(outputs[output], val);
            //Console.WriteLine("output = " + outputs[output]);
		}
	}
	
	public void defuzzify() {
		// DIR
		float dir_num = 0;
		float dir_den = 0;
		dir_num = 
			outputs[0]*singletons[0] + 
			outputs[1]*singletons[1] + 
			outputs[2]*singletons[2];
		dir_den = 
			outputs[0] + 
			outputs[1] +
			outputs[2];
        if (dir_den != 0) {
            defuzzedDIR = dir_num / dir_den;
        } else {
            defuzzedDIR = 0;
        }

		// SPEED
		float speed_num = 0;
		float speed_den = 0;
		speed_num = 
			outputs[3]*singletons[3] + 
			outputs[4]*singletons[4] + 
			outputs[5]*singletons[5];
		speed_den = 
			outputs[3] + 
			outputs[4] +
			outputs[5];
        if (speed_den != 0) {
            defuzzedSPEED = speed_num / speed_den;
        } else {
            defuzzedSPEED = 0;
        }

		// ATTACKRATE
		float attack_num = 0;
		float attack_den = 0;
		attack_num = 
			outputs[6]*singletons[6] + 
			outputs[7]*singletons[7] + 
			outputs[8]*singletons[8];
		attack_den = 
			outputs[6] + 
			outputs[7] +
			outputs[8];
        if (attack_den != 0) {
            defuzzedATTACK = attack_num / attack_den;
        } else {
            defuzzedATTACK = 0;
        }

        Console.WriteLine("attack is " + defuzzedATTACK);
        Console.WriteLine("speed is " + defuzzedSPEED);
        Console.WriteLine("dir is " + defuzzedDIR);

        //Debug.Log("attack is " + defuzzedATTACK);
        //Debug.Log("speed is " + defuzzedSPEED);
        //Debug.Log("dir is " + defuzzedDIR);
	}
}
	
class Rule {
    private int rOutput;
    private int[][] rInputs;
	private FuzzyLogic owner;
	private bool[][] nots;
		
	public Rule(int numRules, int output, FuzzyLogic o) {
        rInputs = new int[numRules][];
		nots = new bool[numRules][];
		rOutput = output;
		owner = o;
	}

    public void setTerm(int index, int[] terms, bool[] nots) {
		rInputs[index] = terms;	
		this.nots[index] = nots;
	}

    public int getOutput() {
		return rOutput;	
	}
		
	public float evalRule() {
		//evaluate rule here
		float x = 0;
		for (int j = 0; j < rInputs.Length; j++) {
			float[] vals = new float[rInputs[j].Length];
			for (int i = 0; i < rInputs[j].Length; i++) {
				if (nots[j][i]) {
					vals[i] = FuzzyLogic.NOT(owner.inputs[(int)rInputs[j][i]]);
				} else {
					vals[i] = owner.inputs[(int)rInputs[j][i]];
				}
			}
			x = FuzzyLogic.OR(x, FuzzyLogic.AND(vals));	
		}
		return x;
	}
}

class RuleReader {
    private Dictionary<string, int> inputs;
    private Dictionary<string, int> outputs;
    private int inputNum;
    private int outputNum;
    private List<Rule> rules;
    private Regex inputExp;
    private Regex outputExp;
    private Regex ruleExp;
    private Regex varExp;
    private FuzzyLogic logic;

    public RuleReader(string file, FuzzyLogic logic) {
        inputs = new Dictionary<string, int>();
        outputs = new Dictionary<string, int>();
        inputNum = 0;
        outputNum = 0;
        StreamReader reader = new StreamReader(file);
        rules = new List<Rule>();
        string[] contents = reader.ReadToEnd().Split('\n');

        inputExp = new Regex(@"input ([A-z0-9_]*);");
        outputExp = new Regex(@"output ([A-z0-9_]*);");
        ruleExp = new Regex(@"[A-z0-9_]*\s?=\s?(.*);");
        varExp = new Regex(@"\s?(!?)\s?([A-z0-9_]*)((;|\s)|(\*|\+))");

        this.logic = logic;

        parseContents(contents);
    }

    private void parseContents(string[] contents) {
        foreach (string l in contents) {
            string line = l.ToLower();
            Match inputMatch = inputExp.Match(line);
            Match ouputMatch = outputExp.Match(line);
            Match ruleMatch = ruleExp.Match(line);

            if (inputMatch.Success) {
                try {
                    string name = inputMatch.Groups[1].Value;
                    inputs.Add(name, inputNum++);
                } catch {
                    Console.WriteLine("Unable to parse input definition: " + l);
                }
            } else if (Regex.IsMatch(line, "output .*;")) {
                try {
                    string name = ouputMatch.Groups[1].Value;
                    outputs.Add(name, outputNum++);
                } catch {
                    Console.WriteLine("Unable to parse input definition: " + l);
                }
            } else if (ruleMatch.Success) {
                string[] eqSp = line.Split('=');
                Match var = varExp.Match(eqSp[0]);
                if (var.Success && outputs.ContainsKey(var.Groups[2].Value)) {
                    string[] orSp;
                    if (eqSp[1].Contains("+")) {
                        orSp = eqSp[1].Split('+');
                    } else {
                        orSp = new string[] { eqSp[1] };
                    }
                    Rule r = new Rule(orSp.Length, outputs[var.Groups[2].Value], logic);
                    int count = 0;

                    foreach (string ors in orSp) {
                        string[] andSp;
                        if (ors.Contains("*")) {
                            andSp = ors.Split('*');
                        } else {
                            andSp = new string[] { ors };
                        }
                        int[] inputIndexs = new int[andSp.Length];
                        bool[] inputNots = new bool[andSp.Length];
                        for (int i = 0; i < andSp.Length; i++) {
                            var = varExp.Match(andSp[i] + "*");
                            if (var.Success && inputs.ContainsKey(var.Groups[2].Value)) {
                                inputIndexs[i] = inputs[var.Groups[2].Value];
                                inputNots[i] = var.Groups[1].Value.Length > 0;
                                if (var.Groups[1].Value.Length > 0) {
                                    Console.WriteLine("Not");
                                }
                            } else {
                                if (!var.Success) {
                                    Console.WriteLine("No Capture");
                                }
                                Console.WriteLine("Unknown input variable: " + var.Groups[2].Value);
                                Console.WriteLine("in group: " + ors + " on line: " + l);
                            }
                        }
                        r.setTerm(count++, inputIndexs, inputNots);
                    }
                    logic.addRule(r);
                } else {
                    if (var.Success) {
                        Console.WriteLine("Unknown output variable: " + var.Groups[2].Value);
                    } else {
                        Console.WriteLine("Unable to get output name: " + l);
                    }
                }
            }
        }
    }
}
